#include "ImageFilm.h"
#include "../core/filter.h"
#include "../core/point.h"
#include "../core/atomic.h"
#include "../core/imageio.h"


ImageFilm::ImageFilm(int xRes, int yRes, const string &fn, Reference<Filter> ft, const float crop[4]) : Film(xRes, yRes) {
	fileName = fn;
	filter = ft;
	memcpy(cropWindow, crop, 4 * sizeof(float));
	// compute film image extent
	xPixelStart = Ceil2Int(xResolution * cropWindow[0]);
	xPixelCount = Ceil2Int(xResolution * cropWindow[1]) - xPixelStart;
	yPixelStart = Ceil2Int(yResolution * cropWindow[2]);
	yPixelCount = Ceil2Int(yResolution * cropWindow[3]) - yPixelStart;
	// allocate memory
	pixels = new BlockedArray<Pixel>(xPixelCount, yPixelCount);
	rgbs = new BlockedArray<RGBPixel>(xPixelCount, yPixelCount);

	// Initial UI-related stuffs if necessary
	// To do
}

ImageFilm::~ImageFilm() {
	filter = NULL;
	delete  pixels;
	delete  rgbs;
}
void ImageFilm::AddSamples(const Point2 &ImageSample, const Spectrum &L) {
	// Convert from continue to discrete
	float dImageX = ImageSample.x - 0.5;
	float dImageY = ImageSample.y - 0.5;
	
	// Compute the extent of the sample
	
	int xstart = Ceil2Int(dImageX - filter->xWidth);
	int xend = Floor2Int(dImageX + filter->xWidth);
	int ystart = Ceil2Int(dImageY - filter->yWidth);
	int yend = Floor2Int(dImageY + filter->yWidth);
	// Clamp to [0, xRes] x  [0, yRes]

	/*if(xstart < xPixelStart) xstart = xPixelStart;
	if(xstart >= xPixelStart + xPixelCount) xstart = xPixelStart + xPixelCount - 1;
	if(xend < xPixelStart) xend = xPixelStart;
	if(xend >= xPixelStart + xPixelCount) xend = xPixelStart + xPixelCount - 1;
	if(ystart < yPixelStart) ystart = yPixelStart;
	if(ystart >= yPixelStart + yPixelCount) ystart = yPixelStart + yPixelCount - 1;
	if(yend < yPixelStart) yend = yPixelStart;
	if(yend >= yPixelStart + yPixelCount) yend = yPixelStart + yPixelCount - 1;*/

	xstart = max(xstart, xPixelStart);
	xend = min(xend, xPixelStart + xPixelCount - 1);
	ystart = max(ystart, yPixelStart);
	yend = min(yend, yPixelStart + yPixelCount - 1);

	if((xend - xstart) < 0 || (yend - ystart) < 0) {
		Warning("Sample outside image extent\n");
		return;
	}
	
	// precompute filter table offset
	int *ifx = ALLOCA(int, (xend - xstart + 1));
	for(int x = xstart; x <= xend; x++) {
		float fx = fabsf((x - dImageX) * filter->invXWidth * FILTER_TABLE_SIZE);
		ifx[x-xstart] = min(Floor2Int(fx), FILTER_TABLE_SIZE - 1);
	}
	int *ify = ALLOCA(int, (yend - ystart + 1));
	for(int y = ystart; y <= yend; y++) {
		float fy = fabsf((y - dImageY) * filter->invYWidth * FILTER_TABLE_SIZE);
		ify[y-ystart] = min(Floor2Int(fy), FILTER_TABLE_SIZE - 1);
	}
	bool syncNeeded = (filter->xWidth > 0.5f || filter->yWidth > 0.5f);
	// Loop over filter support and add sample to pixel arrays
	for(int x = xstart; x <= xend; x++)
		for(int y = ystart; y <= yend; y++)
		{
			// Get filter value at (x,y)
			int offset = ify[y - ystart] * FILTER_TABLE_SIZE + ifx[x - xstart];
			float filterWt = filter->filterTable[offset];
			// Add sample contribution to pixel(x,y)
			Pixel &pixel = (*pixels)(x - xstart, y - ystart);
			if(!syncNeeded) {
				// pixel.L += L * filterWt;
				pixel.L.AddWeighted(filterWt, L);
				pixel.weightSum += filterWt;
			}
			else {
				// Atomic add needed
				pixel.L.AtomicAddWeighted(filterWt, L);
				atomicAdd(&pixel.weightSum, filterWt);
			}
		}
}

void ImageFilm::GetSampleExtent(int *xStart, int *yStart, int *xEnd, int *yEnd) {
	*xStart = Floor2Int(xPixelStart + 0.5 - filter->xWidth);
	*xEnd = Ceil2Int(xPixelStart + 0.5 + xPixelCount + filter->xWidth);
	*yStart = Floor2Int(yPixelStart + 0.5 - filter->yWidth);
	*yEnd = Ceil2Int(yPixelStart + 0.5 + yPixelCount + yResolution + filter->yWidth);
}

void ImageFilm::Display() {
	// UI related
	// To do
}

void ImageFilm::UpdateDisplay(int x0, int y0, int x1, int y1) {
	// UI related
	// To do
}

void ImageFilm::WriteImage() {
	// Image output
	/*
		void WriteImage(const string &name, float *pixels, float *alpha,
						int XRes, int YRes, int totalXRes, int totalYRes, int xOffset,
						int yOffset);
	*/
	// first convert pixels to frgb-based representation
	float *rgb = new float[3*xPixelCount*yPixelCount];
	float *alpha = new float[xPixelCount*yPixelCount];
	int offset = 0;
	for(int y = 0; y < yPixelCount; y++) {
		for(int x = 0; x < xPixelCount; x++) {
			// Get rbg value
			(*pixels)(x,y).L.ToRGB(&rgb[3*offset]);
			// Get alpha value
			alpha[offset] = (*pixels)(x,y).alpha;
			// Normalize pixel with weight sum
			float weightSum = (*pixels)(x,y).weightSum;
			if(weightSum != 0) {
				float invWt = 1.f / weightSum;
				rgb[3*offset  ] = Clamp(rgb[3*offset  ] * invWt, 0.f, INFINITY);
				rgb[3*offset+1] = Clamp(rgb[3*offset+1] * invWt, 0.f, INFINITY);
				rgb[3*offset+2] = Clamp(rgb[3*offset+2] * invWt, 0.f, INFINITY);
				alpha[offset] = Clamp(alpha[offset] * invWt, 0.f, 1.f);
			}
			offset++;
		}
	}

	::WriteImage(fileName, rgb, alpha, xPixelCount, yPixelCount, xResolution, yResolution, xPixelStart, yPixelStart);

	SAFE_DELETE_ARRAY(rgb);
	SAFE_DELETE_ARRAY(alpha);
}

void ImageFilm::Clear() {
	for(int x = xPixelStart ; x < xPixelStart + xPixelCount; x++)
		for(int y = yPixelStart; y < yPixelStart + yPixelCount; y++)
		{
			Pixel &pixel = (*pixels)(x, y);
			pixel.Clear();
			RGBPixel &rgbpixel = (*rgbs)(x, y);
			rgbpixel.Clear();
		}
}

Spectrum ImageFilm::Get(int x, int y) const {
	if(x >=xPixelStart && x < xPixelStart + xPixelCount && y >=yPixelStart && y < yPixelStart + yPixelCount)
	{
		Pixel &pixel = (*pixels)(x, y);
		return pixel.L;
	}
	else {
		Warning("ExrFile::Get() out of range\n");
		return Spectrum(0.0);
	}
}